package com.jeasonzhao.commons.parser.expression;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import com.jeasonzhao.commons.utils.Algorithms;
import com.jeasonzhao.commons.utils.DataTypes;

public class EvalProviderHelper
{
    private EvalProviderHelper()
    {
        super();
    }

    public static final void addFunctionByClass(AbstractEvalProvider provider
                                                ,String clsName,
                                                String strPrefix,String strSuffix,String ...strSpecialMethods)
        throws EvalException
    {
        try
        {
            Class<?> cls = Class.forName(clsName);
            addFunctionByClass(provider,cls,strPrefix,strSuffix,strSpecialMethods);
        }
        catch(ClassNotFoundException ex)
        {
            throw new EvalException("Can not found the class named \"" + clsName + "\"",ex);
        }
    }

    public static final void addFunctionByClass(AbstractEvalProvider provider,String cls)
        throws EvalException
    {
        addFunctionByClass(provider,cls,null,null);
    }

    public static final void addFunctionByClass(AbstractEvalProvider provider,Class<?> cls)
        throws EvalException
    {
        addFunctionByClass(provider,cls,null,null);
    }

    public static final void addFunctionByClass(AbstractEvalProvider provider,Class<?> cls,
                                                String strPrefix,String strSuffix,String ...strSpecialMethods)
        throws EvalException
    {
        try
        {
            if(null == cls || null == provider)
            {
                return;
            }
            Constructor<?> ctr = cls.getDeclaredConstructor();
            if(null == ctr)
            {
                throw new EvalException("Couldn't found the default constructor for class "
                                        + cls.getName());
            }
            ctr.setAccessible(true);
            Object obj = ctr.newInstance();

            Method[] methods = cls.getDeclaredMethods();
            for(int n = 0;n < methods.length;n++)
            {
                Method m = methods[n];
                int nM = m.getModifiers();
                if(Modifier.isAbstract(nM)
                   || Modifier.isInterface(nM)
                   || Modifier.isNative(nM)
                   || Modifier.isStrict(nM))
                {
                    continue;
                }
                boolean bContains = strSpecialMethods == null || strSpecialMethods.length < 1;
                if(strSpecialMethods != null && strSpecialMethods.length > 0)
                {
                    bContains = false;
                    for(String methodName : strSpecialMethods)
                    {
                        if(methodName != null && methodName.trim().equalsIgnoreCase(m.getName()))
                        {
                            bContains = true;
                            break;
                        }
                    }
                }
                if(bContains == false)
                {
                    continue;
                }
                String strName =
                    ((Algorithms.isEmpty(strPrefix) ? "" : strPrefix.trim())
                     + m.getName()
                     + (Algorithms.isEmpty(strSuffix) ? "" : strSuffix.trim()))
                    .trim();
                addFunctionByMethod(provider,obj,m,strName);
            }
        }
        catch(InvocationTargetException ex)
        {
            throw new EvalException("Error to add new function :" + ex.getMessage(),ex);
        }
        catch(IllegalArgumentException ex)
        {
            throw new EvalException("Error to add new function :" + ex.getMessage(),ex);
        }
        catch(IllegalAccessException ex)
        {
            throw new EvalException("Error to add new function :" + ex.getMessage(),ex);
        }
        catch(InstantiationException ex)
        {
            throw new EvalException("Error to add new function :" + ex.getMessage(),ex);
        }
        catch(SecurityException ex)
        {
            throw new EvalException("Error to add new function :" + ex.getMessage(),ex);
        }
        catch(NoSuchMethodException ex)
        {
            throw new EvalException("Error to add new function :" + ex.getMessage(),ex);
        }
    }

    public static final void addFunctionByMethod(AbstractEvalProvider provider,Object obj,
                                                 String strMethodName,String strFunctionName)
    {
        if(null == provider || null == obj || null == strMethodName
           || strMethodName.trim().length() < 1)
        {
            return;
        }
        Method[] methods = obj.getClass().getDeclaredMethods();
        for(int n = 0;n < methods.length;n++)
        {
            Method m = methods[n];
            if(provider.isIgnoreCase() ? m.getName().equalsIgnoreCase(strMethodName) : m.getName()
               .equals(strMethodName))
            {
                addFunctionByMethod(provider,obj,m,strFunctionName);
            }
        }
    }

    public static final void addFunctionByMethod(AbstractEvalProvider provider,Object obj,Method m)
    {
        addFunctionByMethod(provider,obj,m,null);
    }

    public static final void addFunctionByMethod(AbstractEvalProvider provider,Object obj,
                                                 String strFunctionName)
    {
        addFunctionByMethod(provider,obj,strFunctionName,null);
    }

    public static final void addFunctionByMethod(AbstractEvalProvider provider,Object obj,
                                                 Method m,String strName)
    {
        if(null == obj || null == m || null == provider)
        {
            return;
        }
        if(null == strName || strName.trim().length() < 1)
        {
            strName = m.getName();
        }
        String[] keys = getFunctionSignKeys(strName,m.getParameterTypes());
        if(null == keys || keys.length < 1)
        {
            return;
        }
        IMethodDefination sign = new MethodFunctionSign(obj,m,strName);
        for(int n = 0;null != keys && n < keys.length;n++)
        {
            provider.addFunction(keys[n],sign);
        }
    }

    public static final String[] getFunctionSignKeys(String strFunctionName,
        Class<?>[] parametersDataTypes)
    {
        DataTypes[] dts = new DataTypes[null == parametersDataTypes ? 0
            : parametersDataTypes.length];
        for(int n = 0;null != parametersDataTypes && n < parametersDataTypes.length;n++)
        {
            dts[n] = DataTypes.from(parametersDataTypes[n]);
        }
        return getFunctionSignKeys(strFunctionName,dts);
    }

    public static final String[] getFunctionSignKeys(String strFunctionName,
        ValuePair[] parametersDataTypes)
    {
        DataTypes[] dts = new DataTypes[null == parametersDataTypes ? 0
            : parametersDataTypes.length];
        for(int n = 0;null != parametersDataTypes && n < parametersDataTypes.length;n++)
        {
            dts[n] = parametersDataTypes[n] == null ? DataTypes.OBJECT : parametersDataTypes[n]
                .getDataType();
        }
        return getFunctionSignKeys(strFunctionName,dts);
    }

    public static final String[] getFunctionSignKeys(String strFunctionName,
        Object[] parametersDataTypes)
    {
        DataTypes[] dts = new DataTypes[null == parametersDataTypes ? 0
            : parametersDataTypes.length];
        for(int n = 0;null != parametersDataTypes && n < parametersDataTypes.length;n++)
        {
            dts[n] = DataTypes.from(parametersDataTypes[n] == null ? Object.class
                                    : parametersDataTypes[n].getClass());
        }
        return getFunctionSignKeys(strFunctionName,dts);
    }

    public static final String[] getFunctionSignKeys(String strFunctionName,
        DataTypes[] parametersDataTypes)
    {
        if(Algorithms.isEmpty(strFunctionName))
        {
            return null;
        }
        StringBuffer strParametersKey = new StringBuffer();
        for(int np = 0;null != parametersDataTypes && np < parametersDataTypes.length;np++)
        {
            strParametersKey.append("*" + parametersDataTypes[np].getId());
        }
        int nParamCount = null == parametersDataTypes ? 0 : parametersDataTypes.length;
        return strParametersKey.length() > 0 ? new String[]
            {
            /* Best Match */strFunctionName + "[" + nParamCount + "]" + strParametersKey,
            /* Number Match */strFunctionName + "[" + nParamCount + "]",}
             : new String[]
            {
            /* Number Match */strFunctionName + "[" + nParamCount + "]",};
    }

}
